package org.fujene.communicate.node;

import java.io.IOException;
import java.math.BigInteger;
import java.net.UnknownHostException;
import java.util.Iterator;
import java.util.TreeSet;
import org.fujene.communicate.rpc.PeerHandlerInterface;
import org.fujene.logging.LogParser;
import org.msgpack.rpc.Client;

public class Node implements Iterable<Long>, Cloneable, Comparable<Node>{
	private String IPAddress;
	private int port;
	private Client connection;
	private PeerHandlerInterface worker;
	private TreeSet<Long> hashList;
	private int deadCount;
	private boolean isSuspend;
	private long suspendTime, suspendThreshold;
	private static int threshold;
//	private static int blocks = 1;

	private Node(){/* No default constructor */}

	private Node(String IPaddr, int port){
		this.IPAddress = IPaddr;
		this.port = port;
		this.deadCount = 0;
		this.isSuspend = false;
	}

	public static void property(int division, int deadThreshold){
//		blocks = division;
		threshold = deadThreshold;
	}

	private void createHash(String IPAddress, int port){
		String seed = IPAddress.concat(Integer.toString(port));
		BigInteger tmp = BigInteger.valueOf(seed.hashCode());
//		BigInteger interval = new BigInteger("10000000000000000", 16).divide(BigInteger.valueOf(blocks));

		tmp = tmp.xor(tmp.shiftLeft(32));
		tmp = tmp.xor(tmp.shiftRight(3));
		tmp = tmp.multiply(BigInteger.valueOf(31));
		tmp = tmp.xor(tmp.shiftLeft(1));
		tmp = tmp.xor(tmp.shiftLeft(15));
		tmp = tmp.and(BigInteger.valueOf(0xFFFFFFFFFFFFFFFFL));
		// tmp = tmp.divide(BigInteger.valueOf(blocks));
		this.hashList = new TreeSet<Long>();
		// for(int i = 0; i < blocks; i++){
		this.hashList.add(Long.valueOf(tmp.longValue()));
		// tmp = tmp.add(interval);
		// }
	}

	public static Node create(String IPaddr, int port) throws UnknownHostException{
		Node node = new Node(IPaddr, port);
		node.connection = new Client(IPaddr, port);
		if(node.connection == null) throw new UnknownHostException("Can't connect");
		LogParser.fine("Connected to " + IPaddr + ":" + port);
		node.worker = node.connection.proxy(PeerHandlerInterface.class);
		node.createHash(IPaddr, port);

		return node;
	}

	public static Node createLocal(String IPaddr, int port){
		LogParser.fine("Own node " + IPaddr + ":" + port);
		Node node = new Node(IPaddr, port);
		node.createHash(IPaddr, port);
		return node;
	}

	public static Node createTemp(String IPaddr, int port){
		Node node = new Node(IPaddr, port);
		node.createHash(IPaddr, port);
		return node;
	}

	public boolean isDead(){
		if(++deadCount >= threshold) return true;
		return false;
	}

	public void live(){
		deadCount = 0;
	}

	public void refreshHashTable(){
		createHash(this.IPAddress, this.port);
	}

	public PeerHandlerInterface worker(){
		if(connection == null) try{
			throw new IOException("Client not opened");
		}catch(IOException e){
			e.printStackTrace();
		}
		return worker;
	}

	public long getAttributeHash(long docHash){
		Long val = hashList.ceiling(Long.valueOf(docHash));
		if(val == null) return hashList.first().longValue();
		return val.longValue();
	}

	public String getAddr(){
		return IPAddress;
	}

	public int getPort(){
		return port;
	}

	public Client client(){
		return connection;
	}

	public void closeConnection(){
		connection.close();
	}

	public void setTimeout(int timeout){
		connection.setRequestTimeout(timeout);
	}

	public void resume(){
		isSuspend = false;
	}

	public void suspend(long thresholdSec){
		isSuspend = true;
		suspendTime = System.currentTimeMillis();
		if(thresholdSec > 0) suspendThreshold = suspendTime + (thresholdSec * 1000);
		else suspendThreshold = Long.MAX_VALUE;
	}

	public boolean isExceedsTimeLimit(){
		if(!isSuspend) return false;
		if(System.currentTimeMillis() > suspendThreshold) return true;
		return false;
	}

	public long timeLeft(){
		if(!isSuspend) return 0;
		return (long)((suspendThreshold - System.currentTimeMillis()) / 1000.0);
	}

	public boolean isSuspend(){
		return isSuspend;
	}

	@Override
	public boolean equals(Object obj){
		if(obj instanceof Node)
			return(((Node)obj).getAddr().equals(this.IPAddress) && ((Node)obj).getPort() == this.port);
		return false;
	}

	@Override
	public Iterator<Long> iterator(){
		return hashList.iterator();
	}

	@Override
	public Object clone() throws CloneNotSupportedException{
		Node copiedNode = (Node)super.clone();
		copiedNode.connection = this.connection;
		copiedNode.IPAddress = this.IPAddress;
		copiedNode.hashList = null;
		copiedNode.port = this.port;

		return copiedNode;
	}

	@Override
	protected void finalize() throws Throwable{
		if(connection != null) connection.close();

		super.finalize();
	}

	@Override
	public String toString(){
		return String.format("%s:%d", IPAddress, port);
	}

	@Override
	public int compareTo(Node o){
		return this.hashList.first().compareTo(o.hashList.first());
	}
}
